<h2>Why is this an issue?</h2>
<p>When using the <code>ConcurrentDictionary</code>, there are many overloads of the <code>GetOrAdd</code> and <code>AddOrUpdate</code> methods that
take both a <code>TKey</code> argument and a lambda that expects a <code>TKey</code> parameter. This means that the right side of the lambda can be
written using either the lambda’s parameter or the method’s argument. However, using the method’s argument leads to the lambda capturing it, and the
compiler will need to generate a class and instantiate it before the call. This means memory allocations, as well as more time spend during Garbage
Collection.</p>
<h3>What is the potential impact?</h3>
<p>We measured a significant improvement both in execution time and memory allocation. For more details see the <code>Benchmarks</code> section from
the <code>More info</code> tab.</p>
<h2>How to fix it</h2>
<p>When you are using the <code>ConcurrentDictionary</code> methods <code>GetOrAdd</code> or <code>AddOrUpdate</code>, reference the key by using the
lambda’s parameter instead of the method’s one.</p>
<h3>Code examples</h3>
<h4>Noncompliant code example</h4>
<pre data-diff-id="1" data-diff-type="noncompliant">
Function UpdateValue(dict As ConcurrentDictionary(Of Integer, Integer), key As Integer) As Integer
    Return dict.GetOrAdd(key, Function(k)
                                   Return key + 42
                               End Function)
End Function
</pre>
<h4>Compliant solution</h4>
<pre data-diff-id="1" data-diff-type="compliant">
Function UpdateValue(dict As ConcurrentDictionary(Of Integer, Integer), key As Integer) As Integer
    Return dict.GetOrAdd(key, Function(k)
                                   Return k + 42
                               End Function)
End Function
</pre>
<h2>Resources</h2>
<h3>Documentation</h3>
<ul>
  <li> <a
  href="https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2.getoradd">ConcurrentDictionary&lt;TKey,TValue&gt;.GetOrAdd</a> </li>
  <li> <a
  href="https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2.addorupdate">ConcurrentDictionary&lt;TKey,TValue&gt;.AddOrUpdate</a> </li>
</ul>
<h3>Benchmarks</h3>
<table>
  <colgroup>
    <col style="width: 20%;">
    <col style="width: 20%;">
    <col style="width: 20%;">
    <col style="width: 20%;">
    <col style="width: 20%;">
  </colgroup>
  <thead>
    <tr>
      <th>Method</th>
      <th>Runtime</th>
      <th>Mean</th>
      <th>Standard Deviation</th>
      <th>Allocated</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><p>Capture</p></td>
      <td><p>.NET 7.0</p></td>
      <td><p>68.52 ms</p></td>
      <td><p>4.450 ms</p></td>
      <td><p>88000063 B</p></td>
    </tr>
    <tr>
      <td><p>Lambda</p></td>
      <td><p>.NET 7.0</p></td>
      <td><p>39.29 ms</p></td>
      <td><p>3.712 ms</p></td>
      <td><p>50 B</p></td>
    </tr>
    <tr>
      <td><p>Capture</p></td>
      <td><p>.NET Framework 4.6.2</p></td>
      <td><p>74.58 ms</p></td>
      <td><p>5.199 ms</p></td>
      <td><p>88259787 B</p></td>
    </tr>
    <tr>
      <td><p>Lambda</p></td>
      <td><p>.NET Framework 4.6.2</p></td>
      <td><p>42.03 ms</p></td>
      <td><p>2.752 ms</p></td>
      <td><p>-</p></td>
    </tr>
  </tbody>
</table>
<h4>Glossary</h4>
<ul>
  <li> <a href="https://en.wikipedia.org/wiki/Arithmetic_mean">Mean</a> </li>
  <li> <a href="https://en.wikipedia.org/wiki/Standard_deviation">Standard Deviation</a> </li>
  <li> <a href="https://en.wikipedia.org/wiki/Memory_management">Allocated</a> </li>
</ul>
<p>The results were generated by running the following snippet with <a href="https://github.com/dotnet/BenchmarkDotNet">BenchmarkDotNet</a>:</p>
<pre>
private ConcurrentDictionary&lt;int, string&gt; dict;
private List&lt;int&gt; data;

[Params(1_000_000)]
public int N { get; set; }

[GlobalSetup]
public void Setup()
{
    dict = new ConcurrentDictionary&lt;int, string&gt;();
    data = Enumerable.Range(0, N).OrderBy(_ =&gt; Guid.NewGuid()).ToList();
}

[Benchmark(baseline=true)]
public void Capture()
{
    foreach (var guid in data)
    {
        dict.GetOrAdd(guid, _ =&gt; $"{guid}"); // "guid" is captured
    }
}

[Benchmark]
public void Lambda()
{
    foreach (var guid in data)
    {
        dict.GetOrAdd(guid, x =&gt; $"{x}"); // no capture
    }
}
</pre>
<p>Hardware configuration:</p>
<pre>
BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.2846/22H2/2022Update)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK=7.0.203
  [Host]               : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  .NET 7.0             : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  .NET Framework 4.6.2 : .NET Framework 4.8.1 (4.8.9139.0), X64 RyuJIT VectorSize=256
</pre>

